﻿using System;
using System.Collections.Concurrent;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

namespace SuperX.Helper
{
    public static class ConfigReflectionHelper
    {
        private static readonly ConcurrentDictionary<Type, Dictionary<string, PropertyInfo>> PropertyPairs = new ConcurrentDictionary<Type, Dictionary<string, PropertyInfo>>();

        public static object QueryValueByPath(this object partner, string key)
        {
            if (partner == null || string.IsNullOrEmpty(key))
                return null;
            PropertyInfo propertyInfo = QueryPropertyInfo(partner, key);
            if (propertyInfo != null)
                return propertyInfo.GetValue(partner, null);
            string[] KeyArray = key.Split('.');
            if (KeyArray.Length > 1)
            {
                string Path = string.Join(".", KeyArray.Skip(1));
                return partner?.QueryValueByPath(KeyArray[0]).QueryValueByPath(Path);
            }
            if (Regex.IsMatch(key, "\\[\\S{1,}\\]$"))
            {
                string Value = Regex.Match(key, "\\S{1,}(?=\\[\\S{0,}\\])").Value;
                string Text = Regex.Match(key, "(?<=\\[)\\S{1,}(?=\\])").Value.Replace("\"", "");
                if (string.IsNullOrEmpty(Value))
                {
                    IList list = partner as IList;
                    if (list != null)
                    {
                        int.TryParse(Text, out var result);
                        if (result < list.Count)
                            return list[result];
                    }
                    else
                    {
                        IDictionary dictionary = partner as IDictionary;
                        if (dictionary != null && dictionary.Contains(Text))
                            return dictionary[Convert.ChangeType(Text, partner.GetType().GetGenericArguments()[0])];
                    }
                }
                else
                {
                    object valueByPath = partner.QueryValueByPath(Value);
                    IList list2 = valueByPath as IList;
                    if (list2 != null)
                    {
                        int.TryParse(Text, out var result2);
                        if (result2 < list2.Count)
                            return list2[result2];
                    }
                    else
                    {
                        IDictionary dictionary2 = valueByPath as IDictionary;
                        if (dictionary2 != null && dictionary2.Contains(Text))
                            return dictionary2[Convert.ChangeType(Text, valueByPath.GetType().GetGenericArguments()[0])];
                    }
                }
            }
            return null;
        }

        public static void UpdateValueByPath(this object partner, string key, object value)
        {
            if (partner == null)
                return;

            string[] array = key.Split('.');
            if (array.Length > 1)
            {
                string propertyPath2 = string.Join(".", array.Skip(1));
                partner?.QueryValueByPath(array[0]).UpdateValueByPath(propertyPath2, value);
            }
            else if (Regex.IsMatch(key, "\\[\\d{0,}\\]$"))
            {
                string value2 = Regex.Match(key, "\\S{1,}(?=\\[\\d{0,}\\])").Value;
                int.TryParse(Regex.Match(key, "(?<=\\[)\\d{1,}(?=\\])").Value, out var result);
                if (string.IsNullOrEmpty(value2))
                {
                    IList list = partner as IList;
                    if (list != null && result < list.Count)
                        list[result] = value;
                }
                else
                {
                    IList list2 = QueryPropertyInfo(partner, value2)?.GetValue(partner, null) as IList;
                    if (list2 != null && result < list2.Count)
                        list2[result] = value;
                }
            }
            else if (partner != null)
            {
                var propInfo = QueryPropertyInfo(partner, array[0]);
                if (propInfo.PropertyType.IsEnum)
                {
                    propInfo.SetValue(partner, Enum.Parse(propInfo.PropertyType, value.ToString()), null);
                    return;
                }
                if (propInfo?.PropertyType == typeof(Dictionary<string, string>))
                {
                    var KeyValue = ((string)value).Split('.');
                    var DictionaryValue = (Dictionary<string, string>)propInfo.GetValue(partner, null);
                    DictionaryValue[KeyValue[0]] = KeyValue[1];
                    return;
                }
                if (propInfo.PropertyType != value.GetType())
                {
                    propInfo?.SetValue(partner, Convert.ChangeType(value, propInfo.PropertyType), null);
                    return;
                }
                propInfo?.SetValue(partner, value, null);
            }
        }

        private static PropertyInfo QueryPropertyInfo(object partner, string propertyName)
        {
            if (partner == null)
                return null;
            if (string.IsNullOrEmpty(propertyName))
                return null;
            return QueryPropertyInfo(partner.GetType(), propertyName);
        }

        private static PropertyInfo QueryPropertyInfo(Type type, string propertyName)
        {
            if (type == null)
                return null;
            if (string.IsNullOrEmpty(propertyName))
                return null;
            Dictionary<string, PropertyInfo> propertyInfos = QueryPropertyInfos(type);
            if (propertyInfos != null && propertyInfos.ContainsKey(propertyName))
                return propertyInfos[propertyName];
            return null;
        }

        internal static Dictionary<string, PropertyInfo> QueryPropertyInfos(Type type)
        {
            PropertyPairs.TryGetValue(type, out var value);
            if (value == null)
            {
                ILookup<string, PropertyInfo> lookup = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).ToLookup((PropertyInfo t) => t.Name, (PropertyInfo t) => t);
                Dictionary<string, PropertyInfo> dictionary = new Dictionary<string, PropertyInfo>();
                foreach (IGrouping<string, PropertyInfo> item in lookup)
                    dictionary.Add(item.Key, item.FirstOrDefault());
                PropertyPairs.TryAdd(type, dictionary);
                PropertyPairs.TryGetValue(type, out value);
            }
            return value;
        }

        public static object ChangeType(object value, Type type)
        {
            Type TypeCache = type;
            try
            {
                if (TypeCache.IsGenericType && TypeCache.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
                {
                    if (value == null)
                        return null;
                    TypeCache = Nullable.GetUnderlyingType(TypeCache);
                }
                if (value == null)
                    return null;
                if (TypeCache == value.GetType())
                    return value;
                if (TypeCache.IsEnum)
                {
                    string text = Convert.ToString(value);
                    if (text.Length > 0)
                        return Enum.Parse(TypeCache, text, ignoreCase: true);
                    return null;
                }
                if (Convert.ToString(value).Length > 0)
                    return Convert.ChangeType(value, TypeCache);
                return null;
            }
            catch (Exception)
            {
                return null;
            }
        }

        /// <summary>
        /// 反射赋值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="T1"></typeparam>
        /// <param name="oldObj">需要更新的对象</param>
        /// <param name="newObj">新对象</param>
        public static void AutoMapping<T, T1>(T oldObj, T1 newObj)
        {
            PropertyInfo[] Propertys = newObj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
            Type target = oldObj.GetType();
            foreach (var item in Propertys)
            {
                PropertyInfo targetProp = target.GetProperty(item.Name);
                object value = item.GetValue(newObj, null);
                if (targetProp != null && value != null)
                    targetProp.SetValue(oldObj, value, null);
            }
        }

        /// <summary>
        /// 反射赋值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="T1"></typeparam>
        /// <param name="oldObj">等待修改的对象，需要修改的目标</param>
        /// <param name="newObj">新的对象，新的值</param>
        /// <param name="skip">需要跳过的属性名称</param>
        public static void AutoMapping<T, T1>(T oldObj, T1 newObj, string skip)
        {
            PropertyInfo[] Propertys = newObj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
            Type target = oldObj.GetType();
            foreach (var item in Propertys)
            {
                if (skip.Split(separator: ',').Contains(item.Name)) continue;
                PropertyInfo targetProp = target.GetProperty(item.Name);
                object value = item.GetValue(newObj, null);
                if (targetProp != null && value != null)
                    targetProp.SetValue(oldObj, value, null);
            }
        }
    }
}